#include <boost/filesystem.hpp>
#include<boost/date_time/posix_time/posix_time.hpp>

#include <cereal/types/vector.hpp>
#include <cereal/types/unordered_map.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/archives/binary.hpp>
#include <cereal/archives/portable_binary.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/types/string.hpp>
#include <cereal/cereal.hpp>
#include <boost/program_options.hpp>


#include<iostream>
#include<fstream>
#include<sstream>
#include <map>
#include <vector>
using namespace std;
namespace fs = boost::filesystem;
namespace po = boost::program_options;


enum Type
{
    Dir,
    File
};

class Data
{
public:
    Data(){

    }

    Data(Type _type, std::string &_path, std::string _content):type(_type), path(_path), content(_content){

    }
    ~Data(){

    }

        
    template <class Archive>
    void serialize( Archive & ar )
    {
        ar(cereal::make_nvp("type", type), cereal::make_nvp("path", path), cereal::make_nvp("content", content));
    }

    Type type;  //文档类型 [dir, file]

    std::string path; //文档储存路径

    std::string content; //如果为file，此处存放文件内容
};




class local_history{
public:
    template <class Archive>
    void save( Archive & ar ) const
    {
        ar(cereal::make_nvp("root_dir", root_dir), cereal::make_nvp("data", data), cereal::make_nvp("backup_date", backup_date), cereal::make_nvp("history_file_path", history_file_path));
    }

    template <class Archive>
    void load( Archive & ar )
    {
        ar(cereal::make_nvp("root_dir", root_dir), cereal::make_nvp("data", data), cereal::make_nvp("backup_date", backup_date), cereal::make_nvp("history_file_path", history_file_path));
    }


    std::string root_dir;
    std::vector<Data> data;
    std::string backup_date;
    std::string history_file_path; //磁盘上备份路径
};



string readfile_all_content(const string &file_path){
    ifstream fin(file_path, ios::binary | ios::in);
    stringstream buffer;           
    buffer << fin.rdbuf();         
    string str(buffer.str());
    return str;
}

void back_up(const std::string &root_path);

void load_back_up(const std::string &root_path);

vector<vector<local_history>> get_back_up_info();

void load_history_log_info();

void save_history_log_info();

void init();

void process(int argc, char const *argv[]);

std::vector<string> check_overdue();

void del_overdue();

//<string(hash_code), string(root_path)>
std::map<std::string, std::string> g_history_log;



int64_t get_current_stamp()
{
    time_t t;
    time(&t);
    return t;
}

template <class T>
std::string number_2_str(T val){
    std::ostringstream o;
    o << val;
    return o.str();
}

time_t str_time_to_time_stamp(std::string _time)
{
    struct tm tm;
    memset(&tm, 0, sizeof(tm));

    sscanf(_time.c_str(), "%d-%d-%d %d:%d:%d",
           &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
           &tm.tm_hour, &tm.tm_min, &tm.tm_sec);

    tm.tm_year -= 1900;
    tm.tm_mon--;

    return mktime(&tm);
}

std::string time_stamp_to_string(int64_t time_stamp){
    char now[64];
    time_t tt;
    struct tm *ttime;
    tt = time_stamp;
    ttime = localtime(&tt);
    strftime(now,64,"%Y-%m-%d %H:%M:%S",ttime);
    return std::string(now);
}

#define HASHSIZE 101

/**
 * 获取字符串的hash值
 * @param s
 * @return
 */
unsigned int hash_code(const char *s){
    unsigned int h=0;
    for(; *s; s++)
        h = *s + h*31;
    return h;
}


std::string get_current_date(){
    string strPosixTime = boost::posix_time::to_iso_string(boost::posix_time::microsec_clock::local_time());
    cout << strPosixTime << endl;
    int pos = strPosixTime.find('T');
    strPosixTime.replace(pos,1,std::string("-"));
    strPosixTime.replace(pos + 3,0,std::string(":"));
    strPosixTime.replace(pos + 6,0,std::string(":"));
    return strPosixTime;
}

void create_file(const std::string &file_name){
    std::ofstream fout(file_name);
    fout.close();
}

//历史记录备份文件夹
const std::string history_dir = "LocalHistory";

//历史记录备份log文件
const std::string history_log_file = history_dir + "/history";

//过期时间(天)
const int default_be_overdue_time = 30;

int main(int argc, char const *argv[])
{


    init();


    process( argc, argv);

    return 0;
}


void init(){
    if(!fs::exists(history_dir)){
        fs::create_directories(history_dir);
    }

    if(!fs::exists(history_log_file)){
        create_file(history_log_file);
    }
}


void process(int argc, char const *argv[]){

    po::options_description desc("Allowed options");
    desc.add_options()
            ("i",  po::value<std::string>(), "input path")
            ("r",  po::value<std::string>(), "recover path")
            ("show", "show all backup")
            ("check", "check overdue time")
            ("del", "delete overdue time backup");

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    if (vm.count("i")){
        std::cout << vm["i"].as<std::string>() << std::endl;
        std::string path = vm["i"].as<std::string>();
        std::replace(path.begin(),path.end(),'\\', '/');
        back_up(path);
        return ;
    }

    if (vm.count("show")){
        get_back_up_info();
        return ;
    }

    if (vm.count("r")){
        load_back_up(vm["r"].as<std::string>());
        return ;
    }

    if (vm.count("check")){
        check_overdue();
        return ;
    }

    if (vm.count("del")){
        del_overdue();
        return ;
    }

}


void back_up(const std::string &root_path)
{
    if(!fs::is_directory(root_path)){
        perror("given path is not a directory.");
        exit(-1);
    }

    local_history _local_history;

    _local_history.root_dir = root_path;

    _local_history.backup_date = time_stamp_to_string(get_current_stamp());

    fs::path myPath(root_path);

    if(!boost::filesystem::exists(myPath)){
        perror("targetPath not exists!");
        exit(-1);
    }

    std::string code = number_2_str(hash_code(root_path.c_str()));

    load_history_log_info();

    for(auto &item : g_history_log){
        if((item.first != root_path) && (item.second == code) ){
            perror("hash code clash.");
            exit(-1);
        } else if((item.first == root_path) && (item.second != code)) {
            perror("gen hash code err.");
            exit(-1);
        }
    }

    g_history_log[root_path] = code;
    save_history_log_info();


    fs::recursive_directory_iterator endIter;
    for (fs::recursive_directory_iterator iter(myPath); iter != endIter; iter++) {
        Data data;
        data.path = iter->path().string();
        replace(data.path.begin(),data.path.end(),'\\', '/');
        if (fs::is_directory(*iter)) {
            data.type = Type::Dir;
        } else {
            data.type = Type::File;
            data.content = readfile_all_content(iter->path().string());
        }
        _local_history.data.push_back(data);
    }



    cout << "root_path:" << root_path << " code:" << code << endl;

    std::string path = history_dir + "/" + code;

    if(!fs::exists(path)){
        fs::create_directories(path);
    }

    std::string file_name = number_2_str(get_current_stamp());

    std::string out_path = path + "/" + file_name + ".bin";
    _local_history.history_file_path = out_path;
    {
        std::ofstream os(out_path, std::ios::out | std::ios::binary);
        cereal::BinaryOutputArchive oarchive( os );
        oarchive(cereal::make_nvp("local_history", _local_history));
    }




}

//读取历史记录文件
void load_history_log_info(){
    if(fs::file_size(history_log_file) != 0){
        std::fstream is(history_log_file);
        cereal::JSONInputArchive iarchive(is);
        iarchive(g_history_log);
    }
}

void save_history_log_info(){
    {
        std::ofstream os(history_log_file);
        cereal::JSONOutputArchive oarchive( os );
        oarchive(g_history_log);
    }
}

void load_back_up(const std::string &root_path){

    if(!fs::exists(root_path)){
        perror("given path not exists.");
        exit(-1);
    }

    local_history _local_history;
    {
        std::ifstream is(root_path, std::ios::out | std::ios::binary);
        cereal::BinaryInputArchive iarchive(is);
        iarchive( cereal::make_nvp("local_history", _local_history));
    }

    for(Data data : _local_history.data){
        if(data.type == Type ::Dir){
            fs::create_directories(data.path);
        } else if(data.type == Type::File){
            ofstream fout(data.path, ios::out | ios::binary);
            if(fout){
                fout << data.content;
            } else {
                std::string err = "create file:" + data.path + " err.";
                perror(err.c_str());
                exit(-1);
            }
            fout.close();
        }
    }

    cout << "history load successed.\n";
}

vector<vector<local_history>> get_back_up_info(){

    load_history_log_info();
    vector<vector<local_history>> all_info;

    for (auto &it : g_history_log) {
        string root_path = it.first;
        string history_path = it.second;
        cout << root_path << " ," << history_path << "\n";

        std::string path = history_dir + "/" + history_path;
        fs::recursive_directory_iterator endIter;

        vector<local_history> vec;
        for (fs::recursive_directory_iterator iter(path); iter != endIter; iter++) {
            if (fs::is_regular_file(*iter)) {

                std::string p = iter->path().string();
                std::replace(p.begin(),p.end(),'\\', '/');
                cout << "\t" << p << "->";
                local_history _local_history;
                {
                    std::ifstream is(iter->path().string(), std::ios::in | std::ios::binary);
                    cereal::BinaryInputArchive iarchive(is);
                    iarchive( cereal::make_nvp("local_history", _local_history));
                }
                for(auto &e : _local_history.data){
                    e.content = "";
                }
                cout << _local_history.backup_date << endl;
                vec.push_back(_local_history);
            }
        }
        all_info.push_back(vec);
    }
    if(all_info.empty()){
        cout << "No history to show.\n";
    }

    return all_info;
}


std::vector<string> check_overdue(){
    load_history_log_info();
    vector<vector<local_history>> data = get_back_up_info();
    int64_t current_stamp = get_current_stamp();

    std::vector<string> overdue_path;
    for(auto &vec : data){
        for(auto &history : vec){
            int64_t backup_time = str_time_to_time_stamp(history.backup_date);
            int64_t overdue_time = (default_be_overdue_time * 24 * 60 * 60) + backup_time;

            if(current_stamp > overdue_time){
                overdue_path.push_back(history.history_file_path);
            }
        }
    }

    if(overdue_path.empty()){
        cout << "no history overdue";
        return overdue_path;
    }

    for(auto &f : overdue_path){
        cout << "overdue_path: " << f << endl;
    }
    return overdue_path;
}

void del_overdue(){
    std::vector<string> paths =  check_overdue();
    for(auto &path : paths){
        boost::filesystem::remove(path);
    }

    //如果文件及为空，则删除文件夹，并更新log
    std::vector<string> remove_path;
    std::vector<string> remove_log;
    for(auto &e : g_history_log){
        string name = e.second;
        string path = history_dir + "/" + name;
        int size = 0;
        fs::recursive_directory_iterator endIter;
        for (fs::recursive_directory_iterator iter(path); iter != endIter; iter++) {
            if (fs::is_regular_file(*iter)) {
                size++;
            }
        }
        if(size == 0){
            remove_path.push_back(path);
            remove_log.push_back(e.first);
        }
    }


    for(auto &path : remove_path){
        boost::filesystem::remove(path);
    }

    for(auto &key : remove_log){
        g_history_log.erase(key);
    }

    save_history_log_info();

    cout << "del overdue successed.\n";
}